/**
 * 加密解密工具模块
 * 提供常用的加密解密功能，包括Base64、MD5、SHA系列、AES等
 */

/**
 * Base64编码
 * @param {string} str 要编码的字符串
 * @returns {string} Base64编码后的字符串
 */
export function base64Encode(str) {
  if (typeof str !== 'string') {
    throw new Error('参数必须是字符串');
  }
  try {
    return btoa(unescape(encodeURIComponent(str)));
  } catch (e) {
    throw new Error('Base64编码失败');
  }
}

/**
 * Base64解码
 * @param {string} str Base64编码的字符串
 * @returns {string} 解码后的字符串
 */
export function base64Decode(str) {
  if (typeof str !== 'string') {
    throw new Error('参数必须是字符串');
  }
  try {
    return decodeURIComponent(escape(atob(str)));
  } catch (e) {
    throw new Error('Base64解码失败');
  }
}

/**
 * URL编码
 * @param {string} str 要编码的字符串
 * @returns {string} URL编码后的字符串
 */
export function urlEncode(str) {
  if (typeof str !== 'string') {
    throw new Error('参数必须是字符串');
  }
  return encodeURIComponent(str);
}

/**
 * URL解码
 * @param {string} str URL编码的字符串
 * @returns {string} 解码后的字符串
 */
export function urlDecode(str) {
  if (typeof str !== 'string') {
    throw new Error('参数必须是字符串');
  }
  try {
    return decodeURIComponent(str);
  } catch (e) {
    throw new Error('URL解码失败');
  }
}

/**
 * HTML编码
 * @param {string} str 要编码的字符串
 * @returns {string} HTML编码后的字符串
 */
export function htmlEncode(str) {
  if (typeof str !== 'string') {
    throw new Error('参数必须是字符串');
  }
  const div = document.createElement('div');
  div.textContent = str;
  return div.innerHTML;
}

/**
 * HTML解码
 * @param {string} str HTML编码的字符串
 * @returns {string} 解码后的字符串
 */
export function htmlDecode(str) {
  if (typeof str !== 'string') {
    throw new Error('参数必须是字符串');
  }
  const div = document.createElement('div');
  div.innerHTML = str;
  return div.textContent || div.innerText || '';
}

/**
 * 简单的MD5哈希算法实现
 * @param {string} str 要哈希的字符串
 * @returns {string} MD5哈希值
 */
export function md5(str) {
  if (typeof str !== 'string') {
    throw new Error('参数必须是字符串');
  }

  function rotateLeft(value, amount) {
    const lbits = (value << amount) | (value >>> (32 - amount));
    return lbits;
  }

  function addUnsigned(x, y) {
    const x4 = (x & 0x40000000);
    const y4 = (y & 0x40000000);
    const x8 = (x & 0x80000000);
    const y8 = (y & 0x80000000);
    const result = (x & 0x3FFFFFFF) + (y & 0x3FFFFFFF);
    if (x4 & y4) {
      return (result ^ 0x80000000 ^ x8 ^ y8);
    }
    if (x4 | y4) {
      if (result & 0x40000000) {
        return (result ^ 0xC0000000 ^ x8 ^ y8);
      } else {
        return (result ^ 0x40000000 ^ x8 ^ y8);
      }
    } else {
      return (result ^ x8 ^ y8);
    }
  }

  function F(x, y, z) { return (x & y) | ((~x) & z); }
  function G(x, y, z) { return (x & z) | (y & (~z)); }
  function H(x, y, z) { return (x ^ y ^ z); }
  function I(x, y, z) { return (y ^ (x | (~z))); }

  function FF(a, b, c, d, x, s, ac) {
    a = addUnsigned(a, addUnsigned(addUnsigned(F(b, c, d), x), ac));
    return addUnsigned(rotateLeft(a, s), b);
  }

  function GG(a, b, c, d, x, s, ac) {
    a = addUnsigned(a, addUnsigned(addUnsigned(G(b, c, d), x), ac));
    return addUnsigned(rotateLeft(a, s), b);
  }

  function HH(a, b, c, d, x, s, ac) {
    a = addUnsigned(a, addUnsigned(addUnsigned(H(b, c, d), x), ac));
    return addUnsigned(rotateLeft(a, s), b);
  }

  function II(a, b, c, d, x, s, ac) {
    a = addUnsigned(a, addUnsigned(addUnsigned(I(b, c, d), x), ac));
    return addUnsigned(rotateLeft(a, s), b);
  }

  function convertToWordArray(str) {
    let wordArray = [];
    let wordCount = 0;
    let bytePosition = 0;
    let byteCount = str.length;

    while (bytePosition < byteCount) {
      wordArray[wordCount] = (str.charCodeAt(bytePosition) << (8 * (bytePosition % 4))) | (wordArray[wordCount] || 0);
      if (bytePosition % 4 === 3) wordCount++;
      bytePosition++;
    }

    return { words: wordArray, sigBytes: byteCount };
  }

  function wordToHex(word) {
    let hex = '';
    for (let i = 0; i < 4; i++) {
      let byte = (word >>> (i * 8)) & 0xFF;
      hex += ('0' + byte.toString(16)).slice(-2);
    }
    return hex;
  }

  const utf8String = unescape(encodeURIComponent(str));
  const { words, sigBytes } = convertToWordArray(utf8String);

  const paddedLength = Math.ceil((sigBytes + 9) / 64) * 16;
  words[sigBytes >>> 2] |= 0x80 << (24 - (sigBytes % 4) * 8);
  words[paddedLength - 2] = sigBytes * 8;

  let a = 0x67452301;
  let b = 0xEFCDAB89;
  let c = 0x98BADCFE;
  let d = 0x10325476;

  for (let i = 0; i < paddedLength; i += 16) {
    const oldA = a, oldB = b, oldC = c, oldD = d;

    a = FF(a, b, c, d, words[i], 7, 0xD76AA478);
    d = FF(d, a, b, c, words[i + 1], 12, 0xE8C7B756);
    c = FF(c, d, a, b, words[i + 2], 17, 0x242070DB);
    b = FF(b, c, d, a, words[i + 3], 22, 0xC1BDCEEE);
    a = FF(a, b, c, d, words[i + 4], 7, 0xF57C0FAF);
    d = FF(d, a, b, c, words[i + 5], 12, 0x4787C62A);
    c = FF(c, d, a, b, words[i + 6], 17, 0xA8304613);
    b = FF(b, c, d, a, words[i + 7], 22, 0xFD469501);
    a = FF(a, b, c, d, words[i + 8], 7, 0x698098D8);
    d = FF(d, a, b, c, words[i + 9], 12, 0x8B44F7AF);
    c = FF(c, d, a, b, words[i + 10], 17, 0xFFFF5BB1);
    b = FF(b, c, d, a, words[i + 11], 22, 0x895CD7BE);
    a = FF(a, b, c, d, words[i + 12], 7, 0x6B901122);
    d = FF(d, a, b, c, words[i + 13], 12, 0xFD987193);
    c = FF(c, d, a, b, words[i + 14], 17, 0xA679438E);
    b = FF(b, c, d, a, words[i + 15], 22, 0x49B40821);

    a = GG(a, b, c, d, words[i + 1], 5, 0xF61E2562);
    d = GG(d, a, b, c, words[i + 6], 9, 0xC040B340);
    c = GG(c, d, a, b, words[i + 11], 14, 0x265E5A51);
    b = GG(b, c, d, a, words[i], 20, 0xE9B6C7AA);
    a = GG(a, b, c, d, words[i + 5], 5, 0xD62F105D);
    d = GG(d, a, b, c, words[i + 10], 9, 0x2441453);
    c = GG(c, d, a, b, words[i + 15], 14, 0xD8A1E681);
    b = GG(b, c, d, a, words[i + 4], 20, 0xE7D3FBC8);
    a = GG(a, b, c, d, words[i + 9], 5, 0x21E1CDE6);
    d = GG(d, a, b, c, words[i + 14], 9, 0xC33707D6);
    c = GG(c, d, a, b, words[i + 3], 14, 0xF4D50D87);
    b = GG(b, c, d, a, words[i + 8], 20, 0x455A14ED);
    a = GG(a, b, c, d, words[i + 13], 5, 0xA9E3E905);
    d = GG(d, a, b, c, words[i + 2], 9, 0xFCEFA3F8);
    c = GG(c, d, a, b, words[i + 7], 14, 0x676F02D9);
    b = GG(b, c, d, a, words[i + 12], 20, 0x8D2A4C8A);

    a = HH(a, b, c, d, words[i + 5], 4, 0xFFFA3942);
    d = HH(d, a, b, c, words[i + 8], 11, 0x8771F681);
    c = HH(c, d, a, b, words[i + 11], 16, 0x6D9D6122);
    b = HH(b, c, d, a, words[i + 14], 23, 0xFDE5380C);
    a = HH(a, b, c, d, words[i + 1], 4, 0xA4BEEA44);
    d = HH(d, a, b, c, words[i + 4], 11, 0x4BDECFA9);
    c = HH(c, d, a, b, words[i + 7], 16, 0xF6BB4B60);
    b = HH(b, c, d, a, words[i + 10], 23, 0xBEBFBC70);
    a = HH(a, b, c, d, words[i + 13], 4, 0x289B7EC6);
    d = HH(d, a, b, c, words[i], 11, 0xEAA127FA);
    c = HH(c, d, a, b, words[i + 3], 16, 0xD4EF3085);
    b = HH(b, c, d, a, words[i + 6], 23, 0x4881D05);
    a = HH(a, b, c, d, words[i + 9], 4, 0xD9D4D039);
    d = HH(d, a, b, c, words[i + 12], 11, 0xE6DB99E5);
    c = HH(c, d, a, b, words[i + 15], 16, 0x1FA27CF8);
    b = HH(b, c, d, a, words[i + 2], 23, 0xC4AC5665);

    a = II(a, b, c, d, words[i], 6, 0xF4292244);
    d = II(d, a, b, c, words[i + 7], 10, 0x432AFF97);
    c = II(c, d, a, b, words[i + 14], 15, 0xAB9423A7);
    b = II(b, c, d, a, words[i + 5], 21, 0xFC93A039);
    a = II(a, b, c, d, words[i + 12], 6, 0x655B59C3);
    d = II(d, a, b, c, words[i + 3], 10, 0x8F0CCC92);
    c = II(c, d, a, b, words[i + 10], 15, 0xFFEFF47D);
    b = II(b, c, d, a, words[i + 1], 21, 0x85845DD1);
    a = II(a, b, c, d, words[i + 8], 6, 0x6FA87E4F);
    d = II(d, a, b, c, words[i + 15], 10, 0xFE2CE6E0);
    c = II(c, d, a, b, words[i + 6], 15, 0xA3014314);
    b = II(b, c, d, a, words[i + 13], 21, 0x4E0811A1);
    a = II(a, b, c, d, words[i + 4], 6, 0xF7537E82);
    d = II(d, a, b, c, words[i + 11], 10, 0xBD3AF235);
    c = II(c, d, a, b, words[i + 2], 15, 0x2AD7D2BB);
    b = II(b, c, d, a, words[i + 9], 21, 0xEB86D391);

    a = addUnsigned(a, oldA);
    b = addUnsigned(b, oldB);
    c = addUnsigned(c, oldC);
    d = addUnsigned(d, oldD);
  }

  return wordToHex(a) + wordToHex(b) + wordToHex(c) + wordToHex(d);
}

/**
 * 使用Web Crypto API进行SHA-256哈希
 * @param {string} str 要哈希的字符串
 * @returns {Promise<string>} SHA-256哈希值
 */
export async function sha256(str) {
  if (typeof str !== 'string') {
    throw new Error('参数必须是字符串');
  }

  if (typeof crypto !== 'undefined' && crypto.subtle) {
    const encoder = new TextEncoder();
    const data = encoder.encode(str);
    const hashBuffer = await crypto.subtle.digest('SHA-256', data);
    const hashArray = Array.from(new Uint8Array(hashBuffer));
    return hashArray.map(b => ('00' + b.toString(16)).slice(-2)).join('');
  } else {
    throw new Error('当前环境不支持Web Crypto API');
  }
}

/**
 * 使用Web Crypto API进行SHA-1哈希
 * @param {string} str 要哈希的字符串
 * @returns {Promise<string>} SHA-1哈希值
 */
export async function sha1(str) {
  if (typeof str !== 'string') {
    throw new Error('参数必须是字符串');
  }

  if (typeof crypto !== 'undefined' && crypto.subtle) {
    const encoder = new TextEncoder();
    const data = encoder.encode(str);
    const hashBuffer = await crypto.subtle.digest('SHA-1', data);
    const hashArray = Array.from(new Uint8Array(hashBuffer));
    return hashArray.map(b => ('00' + b.toString(16)).slice(-2)).join('');
  } else {
    throw new Error('当前环境不支持Web Crypto API');
  }
}

/**
 * 简单的AES加密（仅适用于现代浏览器）
 * @param {string} text 要加密的文本
 * @param {string} password 密码
 * @returns {Promise<string>} 加密后的Base64字符串
 */
export async function aesEncrypt(text, password) {
  if (typeof text !== 'string' || typeof password !== 'string') {
    throw new Error('参数必须是字符串');
  }

  if (typeof crypto !== 'undefined' && crypto.subtle) {
    const encoder = new TextEncoder();
    const data = encoder.encode(text);

    // 生成密钥
    const passwordBuffer = encoder.encode(password);
    const hashBuffer = await crypto.subtle.digest('SHA-256', passwordBuffer);
    const key = await crypto.subtle.importKey(
      'raw',
      hashBuffer,
      { name: 'AES-GCM' },
      false,
      ['encrypt']
    );

    // 生成随机IV
    const iv = crypto.getRandomValues(new Uint8Array(12));

    // 加密
    const encrypted = await crypto.subtle.encrypt(
      { name: 'AES-GCM', iv: iv },
      key,
      data
    );

    // 合并IV和加密数据
    const combined = new Uint8Array(iv.length + encrypted.byteLength);
    combined.set(iv);
    combined.set(new Uint8Array(encrypted), iv.length);

    return base64Encode(String.fromCharCode(...combined));
  } else {
    throw new Error('当前环境不支持Web Crypto API');
  }
}

/**
 * 简单的AES解密（仅适用于现代浏览器）
 * @param {string} encryptedText Base64编码的加密文本
 * @param {string} password 密码
 * @returns {Promise<string>} 解密后的文本
 */
export async function aesDecrypt(encryptedText, password) {
  if (typeof encryptedText !== 'string' || typeof password !== 'string') {
    throw new Error('参数必须是字符串');
  }

  if (typeof crypto !== 'undefined' && crypto.subtle) {
    const encoder = new TextEncoder();
    const decoder = new TextDecoder();

    // 解码Base64
    const combined = new Uint8Array([...base64Decode(encryptedText)].map(c => c.charCodeAt(0)));

    // 分离IV和加密数据
    const iv = combined.slice(0, 12);
    const encrypted = combined.slice(12);

    // 生成密钥
    const passwordBuffer = encoder.encode(password);
    const hashBuffer = await crypto.subtle.digest('SHA-256', passwordBuffer);
    const key = await crypto.subtle.importKey(
      'raw',
      hashBuffer,
      { name: 'AES-GCM' },
      false,
      ['decrypt']
    );

    // 解密
    const decrypted = await crypto.subtle.decrypt(
      { name: 'AES-GCM', iv: iv },
      key,
      encrypted
    );

    return decoder.decode(decrypted);
  } else {
    throw new Error('当前环境不支持Web Crypto API');
  }
}

/**
 * 生成随机密码
 * @param {number} length 密码长度，默认12位
 * @param {Object} options 选项
 * @param {boolean} options.includeUppercase 包含大写字母，默认true
 * @param {boolean} options.includeLowercase 包含小写字母，默认true
 * @param {boolean} options.includeNumbers 包含数字，默认true
 * @param {boolean} options.includeSymbols 包含特殊字符，默认true
 * @returns {string} 随机密码
 */
export function generatePassword(length = 12, options = {}) {
  const {
    includeUppercase = true,
    includeLowercase = true,
    includeNumbers = true,
    includeSymbols = true
  } = options;

  let charset = '';
  if (includeUppercase) charset += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
  if (includeLowercase) charset += 'abcdefghijklmnopqrstuvwxyz';
  if (includeNumbers) charset += '0123456789';
  if (includeSymbols) charset += '!@#$%^&*()_+-=[]{}|;:,.<>?';

  if (charset === '') {
    throw new Error('至少需要选择一种字符类型');
  }

  let password = '';
  for (let i = 0; i < length; i++) {
    password += charset.charAt(Math.floor(Math.random() * charset.length));
  }

  return password;
}

/**
 * 计算字符串的CRC32校验值
 * @param {string} str 要计算的字符串
 * @returns {string} CRC32值（十六进制）
 */
export function crc32(str) {
  if (typeof str !== 'string') {
    throw new Error('参数必须是字符串');
  }

  const table = [];
  for (let i = 0; i < 256; i++) {
    let c = i;
    for (let j = 0; j < 8; j++) {
      c = ((c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1));
    }
    table[i] = c;
  }

  let crc = 0 ^ (-1);
  for (let i = 0; i < str.length; i++) {
    crc = (crc >>> 8) ^ table[(crc ^ str.charCodeAt(i)) & 0xFF];
  }

  return ((crc ^ (-1)) >>> 0).toString(16).padStart(8, '0');
}